﻿using crip.Common;
using crip.Helper;
using crip.Models;
using crip.ResourceParameters;
using crip.Services.IRepositorys;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;
using System.Text;

namespace crip.Controllers
{
    /// <summary>
    /// 认证
    /// </summary>
    [Route("api/auth")]
    [ApiController]
    public class AuthenticateController : ControllerBase
    {
        #region 依赖注入
        //用户工具包
        private readonly UserManager<CripUser> _userManager;
        //登录工具包
        private readonly SignInManager<CripUser> _signInManager;
        //角色工具包
        private readonly RoleManager<IdentityRole> _roleManager;
        //获取或设置配置值的IConfiguration接口
        private readonly IConfiguration _configuration;
        //缓存验证码
        private readonly IMemoryCache _cache;
        //使用HttpContext的接口
        private readonly IHttpContextAccessor _httpContextAccessor;
        //用户仓库
        private readonly IUserRepository _userRepository;
        public AuthenticateController(
            IConfiguration configuration,
            UserManager<CripUser> userManager,
            SignInManager<CripUser> signInManager,
            RoleManager<IdentityRole> roleManager,
            IMemoryCache cache,
            IHttpContextAccessor httpContextAccessor,
            IUserRepository userRepository
        )
        {
            _configuration = configuration;
            _userManager = userManager;
            _signInManager = signInManager;
            _roleManager = roleManager;
            _cache = cache;
            _httpContextAccessor = httpContextAccessor;
            _userRepository = userRepository;

        }
        #endregion

        #region 登录
        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="loginParameter"></param>
        /// <returns></returns>
        [HttpPost("Login")]//     auth/login
        public async Task<IActionResult> LoginAsync([FromBody] LoginParameter loginParameter)
        {

            #region 参数解释
            /*1.验证用户名密码
               参数
             userName
             String
             登录所用的用户名。
             password
             String
             尝试登录时所用的密码。
             isPersistent
             Boolean
             指示在关闭浏览器后登录 cookie 是否应保持不变的标志。
             lockoutOnFailure
             Boolean
             指示登录失败时是否应锁定用户帐户的标志。
            */
            #endregion

            #region 1.验证数据
            var loginResult = await _signInManager.PasswordSignInAsync(
                loginParameter.Email,
                loginParameter.Password,
                false,
                false
                );
            PagesResponse pagesResponse = new PagesResponse();

            if (!loginResult.Succeeded)
            {
                pagesResponse.BadRequest("账号或密码错误");
                return BadRequest(pagesResponse);
            }
            #endregion

            #region 2.创建JWT
            //验证成功取得数据
            var user = await _userManager.FindByEmailAsync(loginParameter.Email);//视频使用FindByName

            #region header
            var signingAlgorithm = SecurityAlgorithms.HmacSha256;
            #endregion

            #region payload
            var claims = new List<Claim>//直接使用[]不可使用Add（因为是数组）
            {
                new Claim(JwtRegisteredClaimNames.Sub,user.Id),// id的专有名称 Sub
               // new Claim(ClaimTypes.Role,"Admin")
            };
            var RoleNames = await _userManager.GetRolesAsync(user);//获得角色列表
            
            foreach (var roleName in RoleNames)
            {
                var roleClaim = new Claim(ClaimTypes.Role, roleName);
                Console.WriteLine( roleClaim );
                claims.Add(roleClaim);
            }
            #endregion

            #region signiture
            //需要读取appsetting配置文件，所以要把配置文件服务的依赖通过IOC容器从构建函数注入进来
            var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);//将私钥以UTF8编码并输出字节
            var signingKey = new SymmetricSecurityKey(secretByte);//然后使用非对称算法对私钥进行加密
            var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //最后使用HS256验证一下非对称加密后的私钥,SigningCredentials表示用于生成数字签名的加密密钥和安全算法。
            var token = new JwtSecurityToken(

                    issuer: _configuration["Authentication:Issuer"],
                    audience: _configuration["Authentication:Audience"],

                    claims,

                    notBefore: DateTime.Now,//发布时间
                    expires: DateTime.Now.AddHours(12),//设置有效期时间

                    signingCredentials//数字签名
                    /*
                    issuer:"fakexiecheng.com",//发布token者
                    audience:"fakexiecheng.com",//发布给,对于我们项目而言，应当是项目前端，
                    因为我们将频繁使用issuer与audience，我们可以给该俩对象转移到配置文件中
                     */
                    );//通过数据创建Jwt token
            var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);//使用JwtSecurityTokenHandler来以字符串的形式输出token
            #endregion

            #endregion

            #region 3.封装数据
            var role = RoleNames[0]; // _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.Role);//获得角色  
            var roleValue = role==null?"Error":role;
            #region 测试内容-响应头设置
            /*
            IdentityUser currentUser = await _userManager.GetUserAsync(HttpContext.User); 获取当前登录用户
            HttpResponseHelper httpResponseHelper = new HttpResponseHelper();
            httpResponseHelper.SetHeader("Role", roleValue,Response);
            httpResponseHelper.SetHeader("LoginToken",tokenStr,Response);
            */

            /*
            Response.Headers.Add("Role", roleValue);
            Response.Headers["access-control-expose-headers"] = "Role";

            Response.Headers.Add("LoginToken", tokenStr);
            Response.Headers["access-control-expose-headers"] = "LoginToken";
            */
            #endregion

            var data = new
            {
                Token = tokenStr,
                Role = roleValue,          
            };
            #endregion

            pagesResponse.Success(data, "登录成功");

            return Ok(pagesResponse);
        }
        #endregion

        #region 注册
        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="registerParameter"></param>
        /// <returns></returns>
        [HttpPost("Register")]
        public async Task<IActionResult> RegisterAsync([FromBody] RegisterParameter registerParameter)
        { 
            PagesResponse pagesResponse = new PagesResponse();

            #region 校验
            string code;//验证码
            //如果能从cache获得"邮箱"-对应的验证码 会赋值给code 从而取得，是个hash关系在缓存里，缓存有时效，可以设置
            var IsHaveUser = await  _userManager.FindByEmailAsync(registerParameter.Email);
            if(IsHaveUser != null)
            {
                pagesResponse.BadRequest("该邮箱已被注册");
                return BadRequest(pagesResponse);   
            }
            if (!_cache.TryGetValue(registerParameter.Email, out code))
            {
                pagesResponse.BadRequest("验证码已过期");
                return BadRequest(pagesResponse);
            }
            //校验验证码
            if (registerParameter.Code != code)
            {
                pagesResponse.BadRequest("验证码错误");
                return BadRequest(pagesResponse);
            }
            #endregion
            #region 创建用户
            //新建用户
            CripUser user = new CripUser
            {
                Id = Guid.NewGuid().ToString(),
                UserName = registerParameter.Email,
                Email = registerParameter.Email
            };
            //将用户插入到表里，并Hash密码
            var Res = await _userManager.CreateAsync(user, registerParameter.Password);

            //给用户添加普通角色
            IdentityUserRole<string> identityUserRole = new IdentityUserRole<string>
            {
                UserId = user.Id,
                RoleId = _configuration["Roles:ordinaryUserGuid"]
            };

            await _userRepository.AddRoleToUserAsync(identityUserRole); //绑定用户角色
            if (!Res.Succeeded)
            {
                pagesResponse.BadRequest("创建失败，请检查格式");
                return BadRequest(pagesResponse);
            }
            await _userRepository.SaveAsync();
            #endregion

            _cache.Remove(registerParameter.Email);//成功以后清除缓存

            pagesResponse.Success("注册成功");
            return Ok(pagesResponse);
        }
        #endregion

        #region 重置密码
        /// <summary>
        /// 重置密码
        /// </summary>
        /// <param name="forgetPasswordParameter"></param>
        /// <returns></returns>
        [HttpPost("Password")]
        public async Task<IActionResult> ChangePasswordAsync([FromBody] ChangePasswordParameter forgetPasswordParameter)
        {
            PagesResponse pagesResponse = new PagesResponse();

            #region 校验
            string code;//验证码
            //如果能从cache获得"邮箱"-对应的验证码 会赋值给code 从而取得，是个hash关系在缓存里，缓存有时效，可以设置
            if (!_cache.TryGetValue(forgetPasswordParameter.Email, out code))
            {
               pagesResponse.BadRequest("验证码已过期");
                return BadRequest(pagesResponse);
            }
            //校验验证码
            if (forgetPasswordParameter.Code != code)
            {
                pagesResponse.BadRequest("验证码错误");
                return BadRequest(pagesResponse);
            }
            var user = await _userManager.FindByEmailAsync(forgetPasswordParameter.Email);//通过邮件获得用户
            if (user == null)
            {
                pagesResponse.NotFound("没有该用户");
                return BadRequest(pagesResponse);
            }
            #endregion
            #region 重置密码
            string token = await _userManager.GeneratePasswordResetTokenAsync(user);//生成密码重置token
            IdentityResult res = await _userManager.ResetPasswordAsync(user, token, forgetPasswordParameter.Password);   //重置密码

            if (!res.Succeeded)
            {
                pagesResponse.BadRequest("重置失败");
                return BadRequest(pagesResponse);
            }
            #endregion

            _cache.Remove(forgetPasswordParameter.Email);//成功以后清除缓存

            pagesResponse.Success("重置成功");
            return Ok(pagesResponse);
        }
#endregion
    }
}

